#include <iostream>
#include <pthread.h>
#include <string>
#include <queue>
#include <unistd.h>


class BlockingQueue
{
public:
    BlockingQueue(int cap = 5)
        : _capacity(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);

        pthread_cond_init(&produce_cond, nullptr);
        pthread_cond_init(&consume_cond, nullptr);
    }

    void Put(int val)
    {
        pthread_mutex_lock(&_mutex);
        while (_queue.size() == _capacity)
        {
            pthread_cond_wait(&produce_cond, &_mutex);
            pthread_cond_signal(&consume_cond);
        }
        _queue.push(val);
        pthread_mutex_unlock(&_mutex);

        pthread_cond_signal(&consume_cond);
    }

    void Take(int& val)
    {
        pthread_mutex_lock(&_mutex);
        while (_queue.size() == 0)
        {
            pthread_cond_wait(&consume_cond, &_mutex);
            pthread_cond_signal(&produce_cond);
        }

        val = _queue.front();
        _queue.pop();

        pthread_mutex_unlock(&_mutex);
        pthread_cond_signal(&produce_cond);
    }

    ~BlockingQueue()
    {
        pthread_mutex_destroy(&_mutex);

        pthread_cond_destroy(&produce_cond);
        pthread_cond_destroy(&consume_cond);
    }

private:
    std::queue<int> _queue;
    int _capacity;
    pthread_mutex_t _mutex;
    pthread_cond_t produce_cond;
    pthread_cond_t consume_cond;
};

BlockingQueue blockqueue(5);

void* Produce(void* args)
{
    for (int i = 0; i < 10; i++)
    {
        std::cout << "produce: " << i << std::endl;
        blockqueue.Put(i);
        sleep(1);
    }
}

void* Consume(void* args)
{
    for (int i = 0; i < 10; i++)
    {
        int value;
        blockqueue.Take(value);
        std::cout << "consume: " << value << std::endl;
        sleep(2);
    }
}

int main()
{
    pthread_t producer;
    pthread_t consumer;

    pthread_create(&producer, nullptr, Produce, nullptr);
    pthread_create(&consumer, nullptr, Consume, nullptr);

    pthread_join(producer, nullptr);
    pthread_join(consumer, nullptr);
    
    return 0;
}